import math
import os
import sys
import ui as pui
import wx
import sys
import setting
import cv2
import json
import scipy
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from scipy.optimize import leastsq
from scipy.signal import peak_widths
from scipy.signal import find_peaks
from scipy.optimize import curve_fit
from PyCameraList.camera_device import test_list_cameras, list_video_devices, list_audio_devices
import ctypes
from ctypes import c_void_p, c_int, byref, create_string_buffer, wintypes
from itertools import combinations

# 判断是否为打包状态
if getattr(sys, 'frozen', False):
	# 如果是打包状态，使用打包路径
	bundle_dir = sys._MEIPASS
else:
	# 如果不是打包状态，使用当前路径
	bundle_dir = os.path.dirname(os.path.abspath(__file__))

class MainFrame(wx.Frame):
	def __init__(self, parent):
		wx.Frame.__init__(self, parent, -1, setting.APP_TITLE)

		#return: {0: 'Intel(R) RealSense(TM) 3D Camera (Front F200) RGB', 1: 'NewTek NDI Video', 2: 'Intel(R) RealSense(TM) 3D Camera Virtual Driver', 3: 'Intel(R) RealSense(TM) 3D Camera (Front F200) Depth', 4: 'OBS-Camera', 5: 'OBS-Camera2', 6: 'OBS-Camera3', 7: 'OBS-Camera4', 8: 'OBS Virtual Camera'}
		self.dll = ctypes.WinDLL('Dll/SpectroChipsControl.dll')
		self.standard_wavelength = np.array([365.34, 406.15, 436.00, 545.79, 578.60, 696.56, 706.58, 727.17,
											 738.34, 750.66, 763.56, 772.34, 794.56, 800.98, 811.48, 826.63,
											 842.33, 852.20, 866.79, 912.38, 922.18])

		self.file_path = 'config.json'
		self.config = {
			"w1": "",
			"w2": "",
			"w3": "",
			"w4": "",
			"intensity": str(setting.AUTO_DETECT_CENTER),
			"intensity_offset": str(setting.AUTO_DETECT_OFFSET),
		}
		if os.path.exists(self.file_path):
			with open(self.file_path, 'r') as file:
				# self.config = json.load(file)
				file_config = json.load(file)
				self.config = {**self.config, **file_config}  # 覆盖合并

		self.SetBackgroundColour(wx.Colour(240,240,240))
		self.SetSize((1100,860))
		self.InitParams()
		self.Center()
		self.CreateUI()
		# self.CreateUI()
		# self.SearchCamera()
		self.open_camera = False
		self.Bind(wx.EVT_CLOSE, self.OnClose)
	def OnClose(self, event):
		if hasattr(self, 'camera') and hasattr(self, 'timer') and self.timer!=None:
			self.timer.Stop()  # 停止计时器
		self.Destroy()  # 销毁窗口
	def InitParams(self):
		Signal = pui.Signal
		self.log_list = []
		self.log = Signal("")
		
		# ui用的信号
		self.auto_roi = Signal(False)
		self.auto_detect = Signal(False)
		self.exp = Signal(setting.INIT_BRIGHTNESS, setting.INIT_BRIGHTNESS, lambda v: max(0, min(v, setting.MAX_BRIGHTNESS)), self.UpdateExp)
		self.roi = Signal(0)
		self.roi_width = Signal(setting.INIT_ROI_WIDTH, setting.INIT_ROI_WIDTH)
		self.gain = Signal(setting.INIT_GAIN, setting.INIT_GAIN, lambda v: max(1, min(v, setting.MAX_GAIN)), self.UpdateGain)
		self.intensity = Signal(self.config["intensity"])
		self.intensity_offset = Signal(self.config["intensity_offset"])
		self.w1 = Signal(self.config["w1"])
		self.w2 = Signal(self.config["w2"])
		self.w3 = Signal(self.config["w3"])
		self.w4 = Signal(self.config["w4"])
		self.range_start = Signal(setting.INIT_RANGE_START, setting.INIT_RANGE_START, lambda v: max(0, min(v, setting.MAX_RANGE)))
		self.range_end = Signal(setting.INIT_RANGE_END, setting.INIT_RANGE_END, lambda v: max(0, min(v, setting.MAX_RANGE)))
		self.avg = Signal(0, 0)
		self.max = Signal(0, 0)
		self.chip_id = Signal(setting.DEFAULT_CHIP_ID)
		self.rgb = Signal([])
		self.camera_id = Signal(-1)

		self.save = False

	def SearchCamera(self):
		self.Log("搜索光譜儀...")
		
		cameras = dict(list_video_devices())
		for key in cameras:
			self.Log(str(key)+":"+cameras[key])
		for key in cameras:
			if cameras[key] == "PC Camera":
				self.camera_id.value = key
				self.Log(str(key)+":"+cameras[key])
				break
				
		# for i in range(4):
		# 	camera = cv2.VideoCapture(i)
		# 	print(i,camera.isOpened())
		# 	if camera.isOpened():
		# 		if (
		# 			camera.get(cv2.CAP_PROP_FRAME_WIDTH) == 1280.0
		# 			and camera.get(cv2.CAP_PROP_FRAME_HEIGHT) == 800.0
		# 			and camera.get(cv2.CAP_PROP_CONVERT_RGB) == 1.0
		# 			and camera.get(cv2.CAP_PROP_GAIN) == 6.0
		# 			and camera.get(cv2.CAP_PROP_FPS) == 10.0
		# 		):
		# 			self.camera_id = i
		# 			break
		# 		camera.release()
		if self.camera_id() != -1:
			self.Log("找到光譜儀")
		else:
			self.Log("未找到光譜儀")
	def OpenCamera(self):
		if self.camera_id() == -1:
			self.Log("未找到光譜儀")
			return
		self.open_camera = True

		self.camera = cv2.VideoCapture(self.camera_id())
		if not self.camera.isOpened():
			self.Log("無法打開攝像頭")
			raise ValueError("無法打開攝像頭")
		
		self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, setting.FRAME_WIDTH)
		self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, setting.FRAME_HEIGHT)
		self.camera.set(cv2.CAP_PROP_MODE, 2)
		self.camera.set(cv2.CAP_PROP_CONVERT_RGB, 0)
		self.camera.set(cv2.CAP_PROP_BRIGHTNESS, self.exp())
		self.camera.set(cv2.CAP_PROP_GAIN, self.gain())

		self.timer = wx.Timer(self)
		self.Bind(wx.EVT_TIMER, self.UpdateCamera, self.timer)
		self.timer.Start(setting.UPDATE_DELAY)
		self.search_roi_record = 0
		self.auto_detect_record = 0
		self.Log("打開攝像頭")
	def CloseCamera(self):
		if self.camera_id == -1:
			self.Log("未找到光譜儀")
			return
		if self.open_camera:
			self.open_camera = False
			self.camera.release()
			self.camera.set(cv2.CAP_PROP_BRIGHTNESS, 6.0)
			self.camera.set(cv2.CAP_PROP_GAIN, 10.0)
			self.timer.Stop()
			self.timer = None
			self.Log("關閉攝像頭")
			# self.SaveData()
	def AutoDetect(self):
		self.auto_detect.value = True
	# 循环更新相机
	def UpdateCamera(self, evt):
		# Read the camera frame
		ret, frame = self.camera.read()
		self.frame = frame

		if ret:
			# rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
			# rgb = cv2.resize(rgb, (setting.RAW_WIDTH, setting.RAW_HEIGHT))
			# height, width = rgb.shape[:2]
			# top = int((self.ROI + self.roi_width) * setting.RAW_HEIGHT / setting.FRAME_HEIGHT)
			# bottom = int((self.ROI - self.roi_width) * setting.RAW_HEIGHT / setting.FRAME_HEIGHT)
			# cv2.rectangle(rgb, (0, top), (width, bottom), (255, 0, 0), 1)
			# self.raw.SetBitmap(wx.Bitmap.FromBuffer(width, height, rgb))

			a = frame[0][::2].astype(np.uint16)  # 從0開始，每2個元素取一個
			b = frame[0][1::2].astype(np.uint16)  # 從1開始，每2個元素取一個
			if len(frame[0]) % 2 != 0:
				b = np.append(b, 0)  # 確保a和b的長度相同

			combined = ((a << 4) | (b - 128)).astype(np.uint16)
			result = combined.reshape(800, 1280).astype(np.float64)

			# if self.save:
			# 	self.save = False
			# 	np.savetxt("AR0144/raw.txt", result, fmt='%f')

			normalized_result = (result / 4096 * 254).astype(np.uint8)
			resized_image = cv2.resize(normalized_result, (setting.RAW_WIDTH, setting.RAW_HEIGHT))
			rgb = cv2.cvtColor(resized_image, cv2.COLOR_BGR2RGB)
			self.frame = cv2.cvtColor(normalized_result, cv2.COLOR_BGR2RGB)

			# 画框
			top = int((self.roi() + self.roi_width()) * setting.RAW_HEIGHT / setting.FRAME_HEIGHT)
			bottom = int((self.roi() - self.roi_width()) * setting.RAW_HEIGHT / setting.FRAME_HEIGHT)
			cv2.rectangle(rgb, (0, top), (setting.RAW_WIDTH, bottom), (255, 0, 0), 1)
			# 显示原始画面
			self.rgb.value = rgb
			# self.raw.SetBitmap(wx.Bitmap.FromBuffer(setting.RAW_WIDTH, setting.RAW_HEIGHT, rgb))

			self.AnalyzeROI(result)


	# 更新exp
	def UpdateExp(self, value):
		if hasattr(self, 'camera') and value != None:
			self.camera.set(cv2.CAP_PROP_BRIGHTNESS, value)
	# 更新gain
	def UpdateGain(self, value):
		if hasattr(self, 'camera') and value != None:
			self.camera.set(cv2.CAP_PROP_GAIN, value)
	# 保存数据
	def SaveValue(self, key, value):
		if value != None:
			self.config[key] = value
			with open(self.file_path, 'w') as f:
				json.dump(self.config, f)
		
	# 確認打開鐳射
	def OpenLaser(self, value):
		self.Log("Laser: " + value)
		self.laser_value = int(value)
		# if value==True:
		# 	dialog = ConfirmDialog(None, title="确认操作")
		# 	result = dialog.ShowModal()
		# 	if result == wx.ID_OK:
		# 		print("用户确认操作")
		# 	else:
		# 		checkBox.SetValue(False)
		# 		print("用户取消操作")
		# 	dialog.Destroy()
		# else:
		# 	checkBox.SetValue(False)
		# 	print("用户取消操作")
	# 分析ROI
	def AnalyzeROI(self, data):
		# 自动ROI
		if self.auto_roi():
			self.search_roi_record = self.search_roi_record + setting.UPDATE_DELAY
			if self.search_roi_record >= setting.SEARCH_ROI_DELAY:
				self.search_roi_record = 0
				auto_roi = self.SearchROI(data)
				self.roi.value = auto_roi
		# 自动调整
		if self.auto_detect():
			self.auto_detect_record = self.auto_detect_record + setting.UPDATE_DELAY
			if self.auto_detect_record >= setting.AUTO_DETECT_DELAY:
				self.auto_detect_record = 0
				self.AutoDetect(data)

		roi_start = max(0, self.roi() - self.roi_width())
		roi_end = min(setting.FRAME_HEIGHT, self.roi() + self.roi_width())
		roi = data[roi_start:roi_end, :]
		roi_avg = np.mean(roi, axis=0)
		
		# print(spep.peakfinder(roi_avg, ranges=[0, 1280], look=10))
		self.data = roi_avg
		if hasattr(self, "dark"):
			roi_avg = roi_avg - self.dark
			self.data_no_dark = roi_avg
		# if hasattr(self, "polynomial"):
		# 	roi_avg = self.polynomial(roi_avg)
		self.DrawPlot(roi_avg)
		
	# 移除暗光谱
	def RemoveDark(self):
		self.dark = self.data
	# 基線擬合
	def Baseline(self):
		# baseObj=BaselineRemoval(self.data)
		# fit = baseObj.ZhangFit()
		# self.base_line = self.data - fit
		# self.Log("基線拟合完成，均值："+ str(np.mean(self.base_line)))
		if hasattr(self, 'xPoly'):
			xindex1 = np.where((self.xPoly > 300) & (self.xPoly < 400))
			xindex2 = np.where((self.xPoly > 700) & (self.xPoly < 900))
			
			# 使用 np.concatenate 合并索引
			xindex = np.concatenate((xindex1[0], xindex2[0]))
			select = self.data_no_dark[xindex]
			self.Log("基線均值："+str(np.average(select)))
			pass
		else:
			self.Log("先進行波長校正")
	def FindPeak(self):
		# baseObj=BaselineRemoval(self.data)
		# fit = baseObj.ZhangFit()
		# self.base_line = self.data - fit
		self.peak = np.argsort(self.data)[-1:]
		# self.peak = np.array([])
		self.peak = np.append(self.peak, np.argmax(self.data[200:400]) + 199)
		self.peak = np.append(self.peak, np.argmax(self.data[400:500]) + 399)
		# self.peak = np.append(self.peak, np.argmax(self.data[700:820]) + 700)
		self.peak = np.append(self.peak, np.argmax(self.data[820:900]) + 819)
		self.peak = np.sort(self.peak)
		# self.peak = scipy.signal.find_peaks(self.data-self.dark, height=200, threshold=2, distance=50)
		# self.peak_width = scipy.signal.peak_widths(self.data, self.peak)
		self.Log("搜寻峰值")
		self.Log(str(self.peak))
		# self.WavelengthCorrection()
		# self.Log(str(self.peak_width))

	def FWHM(self):
		self.FindPeak()
		# 变量储存宽度的数组
		self.fwhm = np.array([])
		# 遍历self.peak
		for i in range(len(self.peak)):
			# 取出self.peak中的值
			x = self.peak[i]
			# 取出self.data中的值
			y = self.data[x]
			# 从x开始向左找到第一个小于y/2的值
			left = x
			while left > 0 and self.data[left] > y*2/3:
				left = left - 1
			# 从x开始向右找到第一个小于y/2的值
			right = x
			while right < len(self.data) and self.data[right] > y*2/3:
				right = right + 1
			# 计算左右两边的距离
			width = right - left
			# 把宽度加入到self.fwhm中
			self.fwhm = np.append(self.fwhm, width*0.75)
		# self.fwhm = peak_widths(self.data, self.peak, rel_height=0.3)
		self.Log(str(self.fwhm))
	def calculate_fwhm(self, spectrum, width, prominence, height, baseline_correction):
		try:
			height_value = float(height)
		except ValueError:
			height_value = None

		if baseline_correction:
			spectrum = spectrum - np.average(spectrum[0:100])

		peaks, _ = find_peaks(spectrum[0:1260], width=width, prominence=prominence, height=height_value, distance=6)
		fwhm_values = []
		for peak in peaks:
			peak_height = spectrum[peak]
			half_height = peak_height / 2.0
			left_idx = peak - np.argmax(spectrum[peak::-1] <= half_height)
			right_idx = peak + np.argmax(spectrum[peak:] <= half_height)
			fwhm = round((right_idx - left_idx), 1)
			fwhm_values.append(fwhm)

		return peaks, fwhm_values
	
	def cubic_fitting(self, peaks):
		if len(peaks) < 3:
			self.Log("Warning, 峰值數量不足，無法進行擬合")
			return None, None, None, None  # 返回四個 None

		# 定義擬合函數（多項式函數）
		def poly_func(x, a, b, c):
			return a * x ** 2 + b * x + c

		# 初始擬合，假設峰值與標準波長按順序匹配
		matched_wavelengths = self.standard_wavelength[:len(peaks)]
		try:
			popt, _ = curve_fit(poly_func, peaks, matched_wavelengths)
			fitted_peaks = poly_func(peaks, *popt)
			r_squared = np.corrcoef(matched_wavelengths, fitted_peaks)[0, 1] ** 2
		except Exception as e:
			self.Log(f"初始擬合失敗: {e}")
			return None, None, None, None  # 返回四個 None

		# 如果擬合優度低於0.9999，嘗試所有可能的標準波長組合
		if r_squared < 0.9999:
			best_r2 = r_squared
			best_popt = popt
			best_fitted_peaks = fitted_peaks
			best_matched_wavelengths = matched_wavelengths

			num_combinations = 0  # 計算已嘗試的組合數
			max_combinations = 100000  # 設置最大嘗試組合數，防止計算時間過長

			# 遍歷所有可能的標準波長組合
			for comb in combinations(self.standard_wavelength, len(peaks)):
				selected_wavelengths = np.array(comb)
				num_combinations += 1

				if num_combinations > max_combinations:
					self.Log("Warning, 嘗試組合數過多，未能找到滿足條件的擬合")
					break

				try:
					popt_tmp, _ = curve_fit(poly_func, peaks, selected_wavelengths)
					fitted_peaks_tmp = poly_func(peaks, *popt_tmp)
					r_squared_tmp = np.corrcoef(selected_wavelengths, fitted_peaks_tmp)[0, 1] ** 2

					if r_squared_tmp > best_r2:
						best_r2 = r_squared_tmp
						best_popt = popt_tmp
						best_fitted_peaks = fitted_peaks_tmp
						best_matched_wavelengths = selected_wavelengths

						if best_r2 >= 0.9999:
							break  # 已經達到目標，退出循環
				except Exception:
					continue  # 如果擬合失敗，跳過這個組合

				if num_combinations % 10000 == 0:
					self.Log(f"已處理 {num_combinations} 個組合，目前最佳 R²: {best_r2:.6f}")

			if best_r2 < 0.9999:
				self.Log("Warning, 無法達到 R² ≥ 0.9999 的擬合")
			return best_popt, best_r2, best_fitted_peaks, best_matched_wavelengths
		else:
			return popt, r_squared, fitted_peaks, matched_wavelengths


	def WavelengthCorrection(self):
		self.FindPeak()
		x = np.array(self.peak)  # x = 80,82,84,86,88,90
		y = np.array([435,546,763,810])
		# 使用polyfit方法来拟合,并选择多项式,这里先使用2次方程
		z1 = np.polyfit(x, y, 3)
		# 使用poly1d方法获得多项式系数,按照阶数由高到低排列
		self.func = np.poly1d(z1)
	def perform_cubic_fitting(self):
		if not hasattr(self, 'data'):
			self.Log("Warning", "No data available for fitting.")
			return

		roi_avg = self.data.copy()
		# if self.baseline_correction.get():
		roi_avg = roi_avg - np.average(roi_avg[0:100])

		peaks, fwhm_values = self.calculate_fwhm(
			roi_avg,
			width=4,
			prominence=7.0,
			height=250,
			baseline_correction=False
		)
		result = self.cubic_fitting(peaks)

		if result is None or any(v is None for v in result):
			self.Log("Warning, 擬合失敗，無法進行後續操作。")
			return

		popt, r_squared, fitted_peaks, matched_wavelengths = result

		result_text = f"Fitting Curve Equation: {popt[0]:.5e}x² + {popt[1]:.5e}x + {popt[2]:.5e}\nR²: {r_squared:.6f}"
		self.Log(result_text)
		self.func = lambda x: popt[0] * x**2 + popt[1] * x + popt[2]
		

		# Convert FWHM from pixels to nm using the derivative of the fitting function
		def poly_derivative(x):
			return 2 * popt[0] * x + popt[1]

		FWHM_convert = [poly_derivative(peak) * fwhm for peak, fwhm in zip(peaks, fwhm_values)]
		# 用文本的形式输出类似表格的内容，将peaks, fitted_peaks, FWHM_convert一行一行打印出来
		fwhm_table = "Peak\tWavelength\tFWHM\n"
		fwhm_table += "\n".join([f"{peak:.2f}\t{fitted_peak:.2f}\t\t{fwhm:.2f}" 
						for peak, fitted_peak, fwhm in zip(peaks, fitted_peaks, FWHM_convert) 
						if fwhm is not None and not math.isnan(fwhm) and fwhm <= 9.5])
		self.Log(fwhm_table)
		# self.update_table_with_fwhm_nm(fwhm_table, peaks, fitted_peaks, FWHM_convert)

	# 雜散光分析
	def StraLight(self):
		left = self.data[self.laser_value-self.range_end():self.laser_value-self.range_start()]
		right = self.data[self.laser_value+self.range_start():self.laser_value+self.range_end()]
		total = left + right
		self.avg.value = round(np.mean(total) / self.data[self.laser_value] * 100, 2)
		self.max.value = round(np.max(total) / self.data[self.laser_value] * 100, 2)
		self.Log("雜散光分析完成，平均值：" + str(self.avg()) + "% 最大值：" + str(self.max())+"%")
	# 画图
	def DrawPlot(self,data):
		self.axes.clear()
		x = np.arange(len(data))
		if hasattr(self, 'func'):
			self.xPoly = self.func(x)
			self.axes.plot(self.xPoly, data)
		else:
			self.axes.plot(x, data)
		self.axes.set_xlim([0, setting.FRAME_WIDTH])
		self.axes.set_ylim([0, 4000])
		self.axes.set_title(f"{self.chip_id()}\nGain: {self.gain()}\nExp: {self.exp()}")
		self.axes.set_xlabel("Wavelength (pixel)")
		self.axes.set_ylabel("Intensity (a.u.)")
		# 把self.peak的值画到图上，用x表示
		if hasattr(self, 'peak'):
			pass
			# self.axes.plot(self.peak, data[self.peak], "x")
		# if hasattr(self, 'peak_width'):
		# 	self.axes.hlines(*self.peak_width[1:], color="C2")
		self.canvas.draw()
		# self.figure.savefig('root/image.png')
	def StartAutoDetect1(self):
		self.exp.value = 0
		self.gain.value = 1
		self.auto_detect.value = True
		self.auto_roi.value = True
	def StartAutoDetect2(self):
		self.exp.value = 0
		self.gain.value = 1
		self.auto_detect.value = True
		# self.auto_roi.value = True
	def CloseAutoDetect1(self):
		self.auto_detect.value = False
		self.auto_roi.value = False
	# 自动调整
	def AutoDetect(self, data):
		# 取出data中的最大值
		maxValue = np.max(data)
		# print(maxValue)
		intensity = int(self.intensity()) if self.intensity() else setting.AUTO_DETECT_CENTER
		offset = int(self.intensity_offset()) if self.intensity_offset() else setting.AUTO_DETECT_OFFSET
		min = intensity - offset
		max = intensity + offset
		if maxValue > max:
			if self.gain() > 1:
				self.gain.value = self.gain() - 1
				self.exp.value = setting.MAX_BRIGHTNESS
			elif self.exp() > 0:
				self.exp.value = self.exp() - 20
			else:
				# 参数都已经最小，结束调整
				pass
		elif maxValue < min:
			if self.exp() < setting.MAX_BRIGHTNESS:
				self.exp.value = self.exp() + 20
			elif self.gain() < setting.MAX_GAIN:
				self.gain.value = self.gain() + 1
				self.exp.value = 0
			else:
				# 参数都已经最大，结束调整
				self.Log("參數已調整到最大值，請檢查光源及光纜連接")
				# self.CloseCamera()
				pass
		else:
			# 參數調整完成
			self.Log("參數調整完成")
			self.auto_roi.value = False
			self.auto_detect.value = False
			self.Log("ROI位置: " + str(self.roi()))
			# 
			# 标准波长（参考波长）
			# standard_wavelengths = np.array([435, 546, 763, 810])
			# # 实际测量的波峰波长
			# measured_peaks = np.array([318, 487, 815, 886])

			# # 使用多项式拟合，这里选择3次多项式，返回多项式系数
			# # polyfit的第三个参数是多项式的次数，可以根据实际情况调整
			# coefficients = np.polyfit(measured_peaks, standard_wavelengths, 3)

			# # 创建一个多项式对象，用于后续计算
			# self.polynomial = np.poly1d(coefficients)

			# 使用拟合得到的多项式模型进行波长校正
			# 假设有一个实际测量的波峰波长列表需要校正
			# actual_peaks_to_correct = np.array([318, 487, 815, 886])
			# corrected_wavelengths = polynomial(actual_peaks_to_correct)

			# 打印校正后的波长
			# print("校正后的波长:", corrected_wavelengths)

			# self.axes.plot(Modpoly_output)
			# self.CloseCamera()
			# print(scipy.signal.find_peaks(data))

			pass

	# 自动搜索最佳ROI
	def SearchROI(self, data):
		roi_match = 0
		roi_total = 0
		for i in range(setting.FRAME_HEIGHT):
			roi_start = max(0, i - self.roi_width())
			roi_end = min(setting.FRAME_HEIGHT, i + self.roi_width())
			roi = data[roi_start:roi_end, :]
			roi_avg = np.mean(roi, axis=0)
			#roi_avg遍历并相加
			temp_total = 0
			for j in range(setting.FRAME_WIDTH):
				temp_total += roi_avg[j]
			if temp_total > roi_total:
				roi_total = temp_total
				roi_match = i
		return roi_match
	# 保存数据
	def SaveData(self):
		# self.save = True
		# 创建一个文件夹
		chip_id = self.chip_id.value
		if chip_id != "" and not os.path.exists(chip_id):
			os.makedirs(chip_id)
		if hasattr(self, 'data'):
			np.savetxt(chip_id + "/raw.csv", np.column_stack((np.arange(1, len(self.data)+1), np.round(self.data, 2))), header="1D,intensity", delimiter=",", fmt="%d,%.2f")
			# np.savetxt(chip_id + "/data.csv", self.data, header="1D,intensity", delimiter=",")
			self.figure.savefig(chip_id + '/figure.png')
			resized_frame = cv2.resize(self.frame, (setting.FRAME_WIDTH, setting.FRAME_HEIGHT))
			cv2.imwrite(chip_id + "/raw.png", resized_frame)
	def Log(self, text):
		self.log_list.append(text)
		if len(self.log_list) > 100:
			self.log_list.pop(0)
		log = ""
		for v in self.log_list:
			log = log + v + "\n"
		self.log.value = log
	def CreateUI(self):
		panel = wx.Panel(self)
		ui = pui.UI(panel)
		Box = ui.Box
		Grid = ui.Grid
		BorderBox = ui.BorderBox
		Label = ui.Label
		CheckBox = ui.CheckBox
		Button = ui.Button
		TextEntry = ui.TextEntry
		Slider = ui.Slider
		Bitmap = ui.Bitmap
		Node = ui.Node

		# 左侧
		self.figure = Figure(figsize=(6,6), dpi=80)
		self.axes = self.figure.add_subplot(111)
		self.axes.set_xlim([0, setting.FRAME_WIDTH])
		self.axes.set_ylim([0, 4000])
		self.axes.set_title(f"{self.chip_id()}\nGain: {self.gain()}\nExp: {self.exp()}")
		self.axes.set_xlabel("Wavelength (pixel)")
		self.axes.set_ylabel("Intensity (a.u.)")
		self.canvas = FigureCanvas(panel, -1, self.figure)

		left = Box(wx.VERTICAL,[
			Label("Chip ID: {s:id}", flag=wx.ALIGN_CENTER, bind=self.chip_id),
			Node(self.canvas),
			Label("ROI       ", flag=wx.ALIGN_RIGHT),
			Box(wx.HORIZONTAL,[
				Bitmap(size=(setting.RAW_WIDTH, setting.RAW_HEIGHT), bgColor=wx.Colour(255,255,255), bind=self.rgb,),
				Slider(maxValue=800, size=(80, setting.RAW_HEIGHT), bind=self.roi,style=wx.SL_VERTICAL|wx.SL_VALUE_LABEL|wx.SL_RIGHT)
			], wx.TOP, 10),
		], wx.ALL, 10)

		# 右侧
		right = Box(wx.VERTICAL,[
			BorderBox("感測器參數", (540,-1), wx.HORIZONTAL, [
				Grid(2, 3, 10, 10, [
					# CheckBox("自動找ROI",bind=self.auto_roi),
					# CheckBox("自動調整參數",bind=self.auto_detect),
					Box(wx.HORIZONTAL, [
						Label("Exp: ", flag=wx.ALIGN_CENTER_VERTICAL),
						TextEntry(bind=self.exp, style=wx.TE_PROCESS_ENTER)
					]),
					Box(wx.HORIZONTAL, [
						Label("ROI寬度: ", flag=wx.ALIGN_CENTER_VERTICAL),
						TextEntry(bind=self.roi_width, style=wx.TE_PROCESS_ENTER)
					]),
					Box(wx.HORIZONTAL, [
						Label("Intensity門檻: ", flag=wx.ALIGN_CENTER_VERTICAL),
						TextEntry(bind=self.intensity, style=wx.TE_PROCESS_ENTER, onchange=lambda evt: self.SaveValue("intensity",evt.GetString()))
					]),
					Box(wx.HORIZONTAL, [
						Label("Gain: ", flag=wx.ALIGN_CENTER_VERTICAL),
						TextEntry(bind=self.gain, style=wx.TE_PROCESS_ENTER)
					]),
					Box(wx.HORIZONTAL, [
						Label("ROI位置: ", flag=wx.ALIGN_CENTER_VERTICAL),
						TextEntry(bind=self.roi, style=wx.TE_PROCESS_ENTER)
					]),
					Box(wx.HORIZONTAL, [
						Label("Intensity範圍: ", flag=wx.ALIGN_CENTER_VERTICAL),
						TextEntry(bind=self.intensity_offset, style=wx.TE_PROCESS_ENTER, onchange=lambda evt: self.SaveValue("intensity_offset",evt.GetString()))
					]),
				])
			], wx.TOP, 10),
			BorderBox("雜散光", (540,-1), wx.HORIZONTAL, [
				Grid(3, 3, 10, 10, [
					Box(wx.HORIZONTAL, [
						CheckBox(group="w",flag=wx.ALIGN_CENTER_VERTICAL, ontoggle=lambda v: self.OpenLaser(self.w1())),
						TextEntry(bind=self.w1, onchange=lambda evt: self.SaveValue("w1",evt.GetString()))
					]),
					Box(wx.HORIZONTAL, [
						CheckBox(group="w",flag=wx.ALIGN_CENTER_VERTICAL, ontoggle=lambda v: self.OpenLaser(self.w2())),
						TextEntry(bind=self.w2, onchange=lambda evt: self.SaveValue("w2",evt.GetString()))
					]),
					Box(wx.HORIZONTAL, [
						Label("Range Start: ", flag=wx.ALIGN_CENTER),
						TextEntry(bind=self.range_start, style=wx.TE_PROCESS_ENTER)
					]),
					Box(wx.HORIZONTAL, [
						CheckBox(group="w",flag=wx.ALIGN_CENTER_VERTICAL, ontoggle=lambda v: self.OpenLaser(self.w3())),
						TextEntry(bind=self.w3, onchange=lambda evt: self.SaveValue("w3",evt.GetString()))
					]),
					Box(wx.HORIZONTAL, [
						CheckBox(group="w",flag=wx.ALIGN_CENTER_VERTICAL, ontoggle=lambda v: self.OpenLaser(self.w4())),
						TextEntry(bind=self.w4, onchange=lambda evt: self.SaveValue("w4",evt.GetString()))
					]),
					Box(wx.HORIZONTAL, [
						Label("Range End: ", flag=wx.ALIGN_CENTER),
						TextEntry(bind=self.range_end, style=wx.TE_PROCESS_ENTER)
					]),
					Label("Avg: {d:avg}%", var={"avg":self.avg}),
					Label("Max: {d:max}%", var={"max":self.max} ),
				])
			], wx.TOP, 10),
			BorderBox("", (540, -1), wx.HORIZONTAL, [
				Grid(1,2,0,40, [
					Box(wx.HORIZONTAL, [
						Label("Chip ID: ",flag=wx.ALIGN_CENTER_VERTICAL),
						TextEntry(bind=self.chip_id),
						Label(" (使用者輸入)",flag=wx.ALIGN_CENTER_VERTICAL),
					]),
					Box(wx.HORIZONTAL, [
						Label("CameraID: ", flag=wx.ALIGN_CENTER_VERTICAL),
						TextEntry(bind=self.camera_id, style=wx.TE_PROCESS_ENTER)
					]),
				])
			], wx.TOP, 10),
			# BorderBox("检测结果", (540, -1), wx.HORIZONTAL, [
			# ], wx.TOP, 10),
			BorderBox("操作區", (540, -1), wx.VERTICAL, [
				Box(wx.HORIZONTAL, [
					Label("光譜儀: ", size=(60, -1), flag=wx.TOP, border=4),
					Grid(3,3,0,0, [
						Button("搜索光譜儀", onclick=lambda v: self.SearchCamera()),
						Button("打開光譜儀", onclick=lambda v: self.OpenCamera()),
						Button("關閉光譜儀", onclick=lambda v: self.CloseCamera()),
						Button("保存", onclick=lambda v: self.SaveData()),
						Button("AutoScaling1", onclick=lambda v: self.StartAutoDetect1()),
						Button("Break", onclick=lambda v: self.CloseAutoDetect1()),
						Button("AutoScaling2", onclick=lambda v: self.StartAutoDetect2()),
						Button("波長校正", onclick=lambda v: self.perform_cubic_fitting()),
					])
				]),
				Box(wx.HORIZONTAL, [
					Label("Baseline: ", size=(60, -1), flag=wx.TOP, border=4),
					Grid(1,3,0,0, [
						# Button("波長校正", onclick=lambda v: self.WavelengthCorrection()),
						Button("記錄暗光譜", onclick=lambda v: self.RemoveDark()),
						Button("基線评估", onclick=lambda v: self.Baseline()),
					])
				]),
				Box(wx.HORIZONTAL, [
					Label("雜散光: ", size=(60, -1), flag=wx.TOP, border=4),
					Grid(2,3,0,0, [
						Button("AutoScaling3", onclick=lambda v: self.StartAutoDetect2()),
						Button("記錄暗光譜", onclick=lambda v: self.RemoveDark()),
						# Button("寻找波峰", onclick=lambda v: self.FindPeak()),
						Button("雜散光分析", onclick=lambda v: self.StraLight()),
					])
				]),
				# Box(wx.HORIZONTAL, [
				# 	Label("FWHM: ", size=(60, -1), flag=wx.TOP, border=4),
				# 	Grid(2,3,0,0, [
				# 		Button("FWHM", onclick=lambda v: self.FWHM()),
				# 	])
				# ])
			]),
			# Grid(2,3,10,10, [
			# 	Button("Baseline", onclick=lambda v: self.Log("asdasa")),
			# 	Button("FWHM"),
			# 	Button("杂散光"),
			# 	Button("Stop"),
			# 	Button("Save"),
			# ], wx.TOP, 10)
			BorderBox("日志", (540, -1), wx.VERTICAL, [
				TextEntry(bind=self.log, size=(500, 200),style=wx.TE_MULTILINE|wx.TE_READONLY, onload=lambda node: node.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL, False, 'Consolas')))
			], wx.TOP, 10),
		], wx.ALL, 10)
		layout = ui.Box(wx.HORIZONTAL, [left,right])
		panel.SetSizer(layout[0])
	
class App(wx.App):
	def OnInit(self):

		self.frame = MainFrame(None)
		self.frame.Show()
		return True

def func(x, p):
    A, k, theta = p
    return A * np.sin(2 * np.pi * k * x + theta)

def residuals(p, y, x):
    return y - func(x, p)

# 确认对话框
class ConfirmDialog(wx.Dialog):
	def __init__(self, parent, title):
		super(ConfirmDialog, self).__init__(parent, title=title, size=(250, 150))
		
		self.panel = wx.Panel(self)
		self.sizer = wx.BoxSizer(wx.VERTICAL)
		
		# 消息
		self.text = wx.StaticText(self.panel, label="確認開啓鐳射？", style=wx.ALIGN_CENTER)
		self.sizer.Add(self.text, 0, wx.ALL | wx.EXPAND, 5)
		
		# 按钮
		self.button_sizer = wx.StdDialogButtonSizer()
		
		self.btn_ok = wx.Button(self.panel, label="確定", id=wx.ID_OK)
		self.button_sizer.AddButton(self.btn_ok)
		
		self.btn_cancel = wx.Button(self.panel, label="取消", id=wx.ID_CANCEL)
		self.button_sizer.AddButton(self.btn_cancel)
		
		self.button_sizer.Realize()
		
		self.sizer.Add(self.button_sizer, 0, wx.ALL | wx.CENTER, 5)
		
		self.panel.SetSizer(self.sizer)
		
		# 绑定事件
		self.Bind(wx.EVT_BUTTON, self.on_confirm, id=wx.ID_OK)
		self.Bind(wx.EVT_BUTTON, self.on_cancel, id=wx.ID_CANCEL)
	
	def on_confirm(self, event):
		# 确认操作
		self.EndModal(wx.ID_OK)
	
	def on_cancel(self, event):
		# 取消操作
		self.EndModal(wx.ID_CANCEL)
app = App()
app.MainLoop()
